#pragma once
#include "Precompiled.h"
#include <ReignMath.hxx>
#include <cmath>

#define GMLabwork06Implementation 0
#define GMLabwork07Implementation 1
#define GMLabwork08Implementation 0

namespace Demo
{
	template <class TPoint>
	inline D2D_POINT_2F operator*= ( D2D_POINT_2F &This, TPoint const &Other )
	{
		This.x *= Other.x;
		This.y *= Other.y;
		return This;
	}

	refclass Renderer : public Sehnsucht::DirectXBase
	{
		typedef std::vector<Reign::Vector2> PointCollection;

		typedef Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> SolidColorBrush;
		typedef Microsoft::WRL::ComPtr<ID2D1Bitmap> Bitmap;
		typedef Bitmap::InterfaceType **BitmapAddress;
		typedef Bitmap::InterfaceType *BitmapPtr;
		typedef Microsoft::WRL::ComPtr<ID2D1PathGeometry> PathGeometry;
		typedef Microsoft::WRL::ComPtr<ID2D1GeometrySink> GeometrySink;
		typedef Microsoft::WRL::ComPtr<IDWriteTextFormat> TextFormat;
		typedef Microsoft::WRL::ComPtr<ID2D1StrokeStyle> StrokeStyle;
		typedef SolidColorBrush::InterfaceType *SolidColorBrushPtr;
		typedef PathGeometry::InterfaceType *PathGeometryPtr;
		typedef GeometrySink::InterfaceType *GeometrySinkPtr;
		typedef StrokeStyle::InterfaceType *StrokeStylePtr;
		typedef TextFormat::InterfaceType *TextFormatPtr;
		typedef concurrency::task<void> EventChain;
		typedef D2D1_RECT_F TextRect;

		typedef Reign::Vector2 Point; // Point coords
		TinyTypeDecl(Point); // Point coords

	internal:

		Renderer();

		void OnPointerPressed( _In_ Demo::Events::AppWindowPointerEventArgsPtr pArgs );
		void OnPointerWheelChanged( _In_ Demo::Events::AppWindowPointerEventArgsPtr pArgs );
		void OnPointerMoved( _In_ Demo::Events::AppWindowPointerEventArgsPtr pArgs );

		void OnWindowsSizeChanged();
		void CreateResources();
		void OnFrameMove( _In_ Demo::TimerViewPtr pTimer );
		void OnFrame();
		void OnExit();

	protectedprivate:

		void BeginDraw2();
		void EndDraw2();

		void DrawGrid();

	private:

		Reign::Vector2 m_f2Scaling;		// Current scaling
		Reign::Vector2 m_f2Offset;		// Current offset 
		Reign::Vector2 m_f2Ptr;			// Current offset

		D2D1_RECT_F m_ActiveRegion;

		Demo::Renderer::PathGeometry m_FunctionGeometry;
		Demo::Renderer::SolidColorBrush m_BlackBrush;
		Demo::Renderer::SolidColorBrush m_GreenBrush;
		Demo::Renderer::SolidColorBrush m_BlueBrush;
		Demo::Renderer::SolidColorBrush m_RedBrush;
		Demo::Renderer::SolidColorBrush m_YellowBrush;
		Demo::Renderer::SolidColorBrush m_OrangeBrush;
		Demo::Renderer::SolidColorBrush m_PinkBrush;
		Demo::Renderer::StrokeStyle m_DashDotStyle;
		Demo::Renderer::StrokeStyle m_DashStyle;
		Demo::Renderer::StrokeStyle m_DotStyle;

		Demo::Renderer::TextFormat m_HUDTxtFmt;
		Demo::Renderer::TextRect m_HUDTxtRct;
		Reign::Standard::String16 m_HUDTxt;

		Demo::Renderer::EventChain m_Chain;

		Bool m_bChainComplete;
		Bool m_bUpdateTarget;

		Demo::Renderer::TextFormat m_MsPtrTxtFmt;
		Demo::Renderer::TextRect m_MsPtrTxtRct;
		Reign::Standard::String16 m_MsPtrTxt;
		Bool m_bDrawMsPtrInfo;
		Bool m_bDrawing;

		Demo::Renderer::EventChain m_RenderingCycles;
		Demo::Renderer::Bitmap m_StartBitmap;

	protectedprivate:

		template <class TPoint>
		inline TPoint ToWindowCoords( 
			_In_ TPoint f2TgtCoords
			)
		{
			using namespace DirectX;
			using namespace Reign;
			using namespace Demo;

			f2TgtCoords *= m_f2Scaling;
			TPoint f2WndCoords;
			f2WndCoords.x =	m_RenderTargetSize.width / 2.0f + f2TgtCoords.x + m_f2Offset.x;
			f2WndCoords.y =	m_RenderTargetSize.height / 2.0f - f2TgtCoords.y + m_f2Offset.y; 
			return f2WndCoords;
		}

		template <class TPoint>
		inline void ToWindowCoords( 
			_In_ TPoint f2TgtCoords,
			_Out_ TPoint &f2WndCoords
			)
		{
			using namespace DirectX;
			using namespace Reign;
			using namespace Demo;

			f2TgtCoords *= m_f2Scaling;
			f2WndCoords.x = m_RenderTargetSize.width / 2.0f + f2TgtCoords.x + m_f2Offset.x;
			f2WndCoords.y = m_RenderTargetSize.height / 2.0f - f2TgtCoords.y + m_f2Offset.y;
		}

		template <class TPoint>
		inline TPoint ToScreenCoords( 
			_In_ TPoint f2WndCoords
			)
		{
			using namespace DirectX;
			using namespace Reign;
			using namespace Demo;

			TPoint f2TgtCoords;
			f2TgtCoords.x =	f2WndCoords.x - m_RenderTargetSize.width / 2.0f;
			f2TgtCoords.y =	m_RenderTargetSize.height / 2.0f - f2WndCoords.y;
			f2TgtCoords *= m_f2Scaling;
			f2TgtCoords += m_f2Offset;
			return f2TgtCoords;
		}

		template <class TPoint>
		inline void ToScreenCoords( 
			_In_ TPoint f2WndCoords,
			_Out_ TPoint &f2TgtCoords
			)
		{
			using namespace DirectX;
			using namespace Reign;
			using namespace Demo;

			f2TgtCoords.x = f2WndCoords.x - m_RenderTargetSize.width / 2.0f; 
			f2TgtCoords.y = m_RenderTargetSize.height / 2.0f - f2WndCoords.y; 
			f2TgtCoords *= m_f2Scaling;
			f2TgtCoords += m_f2Offset;
		}

		template <class TFunc>
		inline void DrawFunction( 
			_In_ Reign::Vector2 f2Range, 
			_In_ UInt16 uDensity, 
			_In_ TFunc pFunc,
			_In_ SolidColorBrushPtr pBrush,
			_In_ Float32 fStroke = 1.8f,
			_In_ StrokeStylePtr pStroke = nullptr
			)
		{
			auto fLength = f2Range.Length();
			auto fStep = fLength / uDensity;

			m_FunctionGeometry = nullptr;

			if ( SUCCEEDED( m_Factory2->CreatePathGeometry(
				m_FunctionGeometry.GetAddressOf())) )
			{
				GeometrySink pSink = nullptr;

				m_FunctionGeometry->Open(pSink.GetAddressOf());
				pSink->SetFillMode(D2D1_FILL_MODE_WINDING);  

				D2D1_POINT_2F f2Pnt, f2ScreenPnt;
				f2Pnt.x = f2Range.x; f2Pnt.y = pFunc(f2Pnt.x);
				ToWindowCoords(f2Pnt, f2ScreenPnt);

				pSink->BeginFigure( f2ScreenPnt, D2D1_FIGURE_BEGIN_HOLLOW );

				f2Pnt.x += fStep;
				f2Pnt.y = pFunc(f2Pnt.x);
				ToWindowCoords(f2Pnt, f2ScreenPnt);

				while ( f2Pnt.x <= (f2Range.y) )
				{
					pSink->AddLine(f2ScreenPnt);
					f2Pnt.x += fStep;
					f2Pnt.y = pFunc(f2Pnt.x);
					ToWindowCoords(f2Pnt, f2ScreenPnt);
				}

				//pSink->AddLine(f2ScreenPnt);
				pSink->EndFigure(D2D1_FIGURE_END_OPEN);
				pSink->Close();

				m_Context2->DrawGeometry(
					m_FunctionGeometry.Get(),
					pBrush,
					fStroke,
					pStroke
					);
			}
		}

		inline void DrawLines( 
			_In_reads_(uNumPoints) D2D1_POINT_2F *pf2Points, 
			_In_ UInt16 uNumPoints, 
			_In_ SolidColorBrushPtr pLineBrush,
			_In_ Float32 fLineStroke = 1.8f,
			_In_ StrokeStylePtr pLineStroke = nullptr
			)
		{
			m_FunctionGeometry = nullptr;

			if ( SUCCEEDED( m_Factory2->CreatePathGeometry(
				m_FunctionGeometry.GetAddressOf())) )
			{
				GeometrySink pLineSink = nullptr;

				m_FunctionGeometry->Open(pLineSink.GetAddressOf());
				pLineSink->SetFillMode(D2D1_FILL_MODE_WINDING);

				UInt16 uPnt = 0;
				D2D1_POINT_2F f2Pnt, f2ScreenPnt;
				f2Pnt = pf2Points[uPnt++];
				ToWindowCoords(f2Pnt, f2ScreenPnt);

				pLineSink->BeginFigure( f2ScreenPnt, D2D1_FIGURE_BEGIN_HOLLOW );

				while ( uPnt < uNumPoints )
				{
					f2Pnt = pf2Points[uPnt++];
					ToWindowCoords(f2Pnt, f2ScreenPnt);
					pLineSink->AddLine(f2ScreenPnt);
				}

				pLineSink->EndFigure(D2D1_FIGURE_END_OPEN);
				pLineSink->Close();

				m_Context2->DrawGeometry(
					m_FunctionGeometry.Get(),
					pLineBrush,
					fLineStroke,
					pLineStroke
					);
			}
		}

		static Float32 Factorial( UInt32 uNum ) 
		{
			static const UInt32 su32Table[] = 
			{
				1.0f, 
#pragma region Factorial Lookup
				1.0f, 
				2.0f, 
				6.0f, 
				24.0f, 
				120.0f, 
				720.0f, 
				5040.0f, 
				40320.0f, 
				362880.0f, 
				3628800.0f, 
				39916800.0f, 
				479001600.0f,
				6227020800.0f,
				87178291200.0f,
				1307674368000.0f,
				20922789888000.0f,
				355687428096000.0f,
				6402373705728000.0f,
				121645100408832000.0f,
				2432902008176640000.0f,
				51090942171709440000.0f,
				1124000727777607680000.0f,
				25852016738884976640000.0f,
				620448401733239439360000.0f,
				15511210043330985984000000.0f,
				403291461126605635584000000.0f,
				10888869450418352160768000000.0f,
				304888344611713860501504000000.0f,
				8841761993739701954543616000000.0f,
				265252859812191058636308480000000.0f,
				8222838654177922817725562880000000.0f,
#pragma endregion
				263130836933693530167218012160000000.0f,
			};

			return su32Table[uNum];
		}

		inline static UInt32 BinomialCoefficient( UInt32 uN, UInt32 uI ) 
		{
			return Factorial(uN) / (Factorial(uI) * Factorial(uN - uI));
		}

		static float Bernstein( Int32 uN, Int32 uI, Float32 fT )
        {
            Float32 fTi = 0.0f;
            Float32 fTni = 0.0f;
            Float32 fBasis = 0.0f;

            if ( fT == 0.0 && uI == 0 )  fTi = 1.0; 
            else fTi = powf( fT, uI );
            if ( uN == uI && fT == 1.0 ) fTni = 1.0; 
            else fTni = powf( (1 - fT), (uN - uI) );

            fBasis = BinomialCoefficient(uN, uI) * fTi * fTni; 
            return fBasis;
        }

		inline static void CalculateBezier( 
			_In_ D2D1_POINT_2F const* pf2InputPoints, 
			_Out_ D2D1_POINT_2F *pf2OutputPoints, 
			_In_ UInt16 uNumInputPoints,
			_In_ UInt16 uNumOutputPoints
			)
		{
			if (!pf2OutputPoints || !uNumOutputPoints || 
				!pf2InputPoints || !uNumInputPoints)
			{
				return;
			}
			else
			{
				Reign::Standard::ZeroValueArray( 
					pf2OutputPoints, uNumOutputPoints
					);
			}

            auto fT = 0.0f;
            auto iCount = 0, jCount = 0;
            auto fStep = 1.0f / (uNumOutputPoints - 1);

            for ( auto i1 = 0; i1 != uNumOutputPoints; i1++)
            { 
                if ((1.0 - fT) < 5e-6) fT = 1.0;
                jCount = 0;

                for ( auto i = 0; i != uNumInputPoints; i++)
                {
                    auto fBasis = Bernstein( uNumInputPoints - 1, i, fT );
                    pf2OutputPoints[iCount].x += fBasis * pf2InputPoints[jCount].x;
                    pf2OutputPoints[iCount].y += fBasis * pf2InputPoints[jCount].y;
                    jCount++;
                }

                iCount++;
                fT += fStep;
            }
		}

		void DrawBezier( 
			_In_reads_(uNumPoints) D2D1_POINT_2F *pf2Points, 
			_In_ UInt16 uNumPoints, 
			_In_ SolidColorBrushPtr pCurveBrush,
			_In_ SolidColorBrushPtr pLineBrush,
			_In_ Float32 fLineStroke = 1.8f,
			_In_ Float32 fCurveStroke = 2.2f,
			_In_ StrokeStylePtr pLineStroke = nullptr,
			_In_ StrokeStylePtr pCurveStroke = nullptr
			)
		{
			UInt16 uNumOutputPoints = uNumPoints * 10ui16;
			D2D1_POINT_2F *pf2OutputPoints = new D2D1_POINT_2F[uNumOutputPoints];

			CalculateBezier(
				pf2Points, pf2OutputPoints, 
				uNumPoints, uNumOutputPoints
				);

			DrawLines(
				pf2Points, uNumPoints, 
				pLineBrush, fLineStroke, 
				pLineStroke
				);

			DrawLines(
				pf2OutputPoints, uNumOutputPoints, 
				pCurveBrush, fCurveStroke, 
				pCurveStroke
				);

			Safe_DeleteArray(pf2OutputPoints);
		}
	};
};

